/**
 * Licensed under the Apache License Version 2.0.
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package net.interknit.knitter.cg.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 *
 */
public class Utils
{
	/**
	 * Given the name of an accessor method name (getter or setter), derive the property name that is being
	 * accessed. For example, if accessorMethodName is getMagicSpell, the returned property name would be
	 * 'magicSpell'.
	 *
	 * @param accessorMethodName
	 *            Name of the getter or setter method
	 * @return Derived name of the property that is being accessed by the given getter or setter method
	 */
	public static String derivePropertyName(String accessorMethodName)
	{
		int index = 0;
		if(accessorMethodName.startsWith("get") || accessorMethodName.startsWith("set"))
		{
			index = 3;
		}
		else if(accessorMethodName.startsWith("is"))
		{
			index = 2;
		}
		else
			throw new IllegalArgumentException(
				accessorMethodName + " is not a valid accessor method name. " +
				"('get', 'is' or 'set' prefix expected)");

		String propertyName = accessorMethodName.substring(index);
		return String.valueOf(propertyName.charAt(0)).toLowerCase() + propertyName.substring(1);
	}

	/**
	 * Given the name of a property, derive the getter method name. If property name is 'magicSpell', the
	 * return value would be 'getMagicSpell'.
	 *
	 * @param propertyName
	 *            Name of the property
	 * @return Name of the derived getter method
	 */
	public static String deriveGetterName(String propertyName)
	{
		return "get" + String.valueOf(propertyName.charAt(0)).toUpperCase() + propertyName.substring(1);
	}

	/**
	 * Given the name of a property, derive the setter method name. If property name is 'magicSpell', the
	 * return value would be 'setMagicSpell'.
	 *
	 * @param propertyName
	 *            Name of the property
	 * @return Name of the derived setter method
	 */
	public static String deriveSetterName(String propertyName)
	{
		return "set" + String.valueOf(propertyName.charAt(0)).toUpperCase() + propertyName.substring(1);
	}

	/**
	 * Given the name of a property, derive the query method name. If property name is 'magicSpell', the
	 * return value would be 'isMagicSpell'.
	 *
	 * @param propertyName
	 *            Name of the property
	 * @return Name of the derived query method
	 */
	public static String deriveQueryMethodName(String propertyName)
	{
		return "is" + String.valueOf(propertyName.charAt(0)).toUpperCase() + propertyName.substring(1);
	}

	/**
	 * Return a string representation of the input Map object as a pretty
	 * format.
	 *
	 * @param map
	 *            Input map
	 * @return String representation of the input Map in a pretty format
	 */
	public static String displayPrettyMap(Map<?, ?> map)
	{
		if(map == null) return "null";
		if(map.isEmpty()) return "";

		StringBuilder sb = new StringBuilder();

		Set<?> keys = map.keySet();

		int maxKeyLen = 0;
		int maxValLen = 0;
		for(Iterator<?> it = keys.iterator(); it.hasNext(); )
		{
			Object obj = it.next();
			String key = String.valueOf(obj);
			String val = String.valueOf(map.get(obj));

			if(key.length() > maxKeyLen) maxKeyLen = key.length();
			if(val.length() > maxValLen) maxValLen = val.length();
		}

		maxKeyLen += 2;
		maxValLen += 2;

		StringBuilder header =
			new StringBuilder().append("|")
			.append(present("KEY", maxKeyLen)).append("|")
			.append(present("VALUE", maxValLen)).append("|");

		StringBuilder hline =
			new StringBuilder().append("|")
			.append(chain("=", maxKeyLen)).append("+")
			.append(chain("=", maxValLen)).append("|");

		StringBuilder rline = new StringBuilder(hline.toString().replace('=', '-'));

		sb.append(hline).append("\n").append(header).append("\n").append(hline).append("\n");

		for(Iterator<?> it = map.keySet().iterator(); it.hasNext(); )
		{
			Object key = it.next();
			String keyStr = String.valueOf(key);
			String valStr = String.valueOf(map.get(key));

			StringBuilder row =
				new StringBuilder().append("|")
				.append(present(keyStr, maxKeyLen)).append("|")
				.append(present(valStr, maxValLen)).append("|");

			sb.append(row).append("\n").append(rline).append("\n");
		}

		return sb.toString();
	}

	/**
	 * Return a string representation of the input Collection object as a pretty
	 * format.
	 *
	 * @param map
	 *            Input collection
	 * @return String representation of the input Collection in a pretty format
	 */
	public static String displayPrettyCollection(Collection<?> collection)
	{
		if(collection == null) return "null";
		if(collection.isEmpty()) return "";

		StringBuilder sb = new StringBuilder();

		int maxValLen = 0;
		for(Iterator<?> it = collection.iterator(); it.hasNext(); )
		{
			String val = String.valueOf(it.next());

			if(val.length() > maxValLen) maxValLen = val.length();
		}

		maxValLen += 2;

		StringBuilder header =
			new StringBuilder().append("|")
			.append(present("VALUE", maxValLen)).append("|");

		StringBuilder hline =
			new StringBuilder().append("|")
			.append(chain("=", maxValLen)).append("|");

		StringBuilder rline = new StringBuilder(hline.toString().replace('=', '-'));

		sb.append(hline).append("\n").append(header).append("\n").append(hline).append("\n");

		for(Iterator<?> it = collection.iterator(); it.hasNext(); )
		{
			String valStr = String.valueOf(it.next());

			StringBuilder row =
				new StringBuilder().append("|")
				.append(present(valStr, maxValLen)).append("|");

			sb.append(row).append("\n").append(rline).append("\n");
		}

		return sb.toString();
	}

	/**
	 * Chain the input str count number of times.
	 *
	 * @param str
	 *            String that needs to be chained
	 * @param count
	 *            Number of times the input string will be chained
	 * @return Chained string. If str is "ab" and count is 3, return value will
	 *         be "ababab"
	 */
	public static String chain(String str, int count)
	{
		String chain = "";
		for(int i = 0; i < count; i++) chain += str;
		return chain;
	}

	/**
	 * Return a filler (spaces) for an input string up to maxLen length.
	 *
	 * @param str
	 *            Input string
	 * @param maxLen
	 *            Number of spaces to return as a filler
	 * @return Filler for the input string up to maxLen length
	 */
	public static String filler(String str, int maxLen)
	{
		String filler = "";
		for(int i = str.length(); i < maxLen; i++) filler += " ";
		return filler;
	}

	/**
	 * Present a string as a fixed length string. If string is shorter than
	 * maxLen, add spaces to it. If the string is longer than maxLen, remove the
	 * training (maxLen - string.length) characters and place ellipses at the
	 * end
	 *
	 * @param str
	 *            String that needs to be presented as a fixed length string
	 *
	 * @param maxLen
	 *            The maximum length of the string
	 *
	 * @return The input string formatted to the fixed length as specified by
	 *         maxLen count
	 */
	public static String present(String str, int maxLen)
	{
		if(str.length() < maxLen)
		{
			return str + filler(str, maxLen);
		}
		else
		{
			return (maxLen - 3) < 0 ? str : (str.substring(0, maxLen - 3) + "...");
		}
	}

	/**
	 * Capitalize the first character of the input string
	 *
	 * @param str
	 *            The input string
	 * @return The input string with the first character capitalized
	 */
	public static String capitalizeFirstChar(String str)
	{
		StringBuilder sb = new StringBuilder();
		sb.append(String.valueOf(str.charAt(0)).toUpperCase());
		sb.append(str.substring(1));
		return sb.toString();
	}

	/**
	 * Clean all occurrences of white-spaces (newline, carriage return, tabs and
	 * spaces) from the given string.
	 *
	 * @param str
	 *            The input string that needs to be cleaned of white-spaces
	 * @return String representing the input str without the white-space
	 *         characters.
	 */
	public static String cleanWhiteSpaces(String str)
	{
		return str.trim().replace('\n', ' ').replace('\r', ' ').replace('\t', ' ').replaceAll(" ", "");
	}

	/**
	 * Create a valid Java variable name from the input string.
	 *
	 * @param str
	 *            The input string from which the Java variable needs to be
	 *            created.
	 * @return A valid Java variable name created from str
	 */
	public static String createValidVariableName(String str)
	{
		final char[] junkChars = {
			'.', '-', '$', '~', '`', '@', '#', '$', '%', '^', '&', '*', '(', ')', '+',
			'{', '}', '[',  ']', '|', '\\', '/', '?', '<', '>', ',', ':', ';', '"', '\''
		};

		str = cleanWhiteSpaces(str);

		for(char junkChar : junkChars)
		{
			str = str.replace(junkChar, '_');
		}

		return str;
	}

	/**
	 * Read the input File as a String. The File will be expected to be an ASCII
	 * file.
	 *
	 * @param file
	 *            The input file to be read
	 * @return String representation of the input file
	 * @throws IOException
	 *             If reading error occurs or if the input file is a binary
	 *             file.
	 */
	public static String readFileAsString(File file) throws IOException
	{
		return readResourceAsString(new FileReader(file));
	}

	/**
	 * Read the input File as a List of string objects, where each element in
	 * the list represents a line of the input file.
	 *
	 * @param file
	 *            The input file to be read
	 * @return List of strings, where each element in the list represents a line
	 *         of the input file, in the original order.
	 * @throws IOException
	 *             If reading error occurs or if the input file is a binary
	 *             file.
	 */
	public static List<String> readFileAsList(File file) throws IOException
	{
		return readResourceAsList(new FileReader(file));
	}

	/**
	 * Read the input Reader as a String.
	 *
	 * @param resourceReader
	 *            The input file to be read
	 * @return String representation of the input Reader
	 * @throws IOException
	 *             If reading error occurs
	 */
	public static String readResourceAsString(Reader resourceReader) throws IOException
	{
		List<String> list = readResourceAsList(resourceReader);

		StringBuilder sb = new StringBuilder();

		for(String line : list) sb.append(line).append("\n");

		return sb.toString();
	}

	/**
	 * Read the input Reader as a List of string objects, where each element in
	 * the list represents a line of the input Reader.
	 *
	 * @param resourceReader
	 *            The input Reader to be read
	 * @return List of strings, where each element in the list represents a line
	 *         of the input Reader, in the original order.
	 * @throws IOException
	 *             If reading error occurs
	 */
	public static List<String> readResourceAsList(Reader resourceReader) throws IOException
	{
		List<String> list = new ArrayList<String>();
		BufferedReader br = new BufferedReader(resourceReader);

		for(; ;)
		{
			String line = br.readLine();
			if(line != null) list.add(line);
			else break;
		}

		return list;
	}

	/**
	 * Format the input comment string to a printable JavaDoc comment.
	 *
	 * @param comments
	 *            The input comment string that needs to be formatted
	 * @param indent
	 *            Indentation level. The formatter will apply that many tabs to
	 *            the output Javadoc. If indent is specified as zero or negative
	 *            value, the Javadoc comments will not have any leading tabs.
	 * @return Formatted Javadoc comments
	 */
	public static String formatJavadoc(String comments, int level)
	{
		return formatJavadoc(comments, level, true);
	}

	/**
	 * Format the input comment string to a printable JavaDoc comment.
	 *
	 * @param comments
	 *            The input comment string that needs to be formatted
	 *
	 * @param indent
	 *            Indentation level. The formatter will apply that many tabs to
	 *            the output Javadoc. If indent is specified as zero or negative
	 *            value, the Javadoc comments will not have any leading tabs.
	 *
	 * @param closeJavadoc
	 *            Should the formatter apply the starting and ending markers? If
	 *            closeJavadoc is true, the output formatted comment will have a
	 *            leading "/_**" (slash-star-star) and a training "*_/"
	 *            (star-slash)
	 *
	 * @return Formatted Javadoc comments
	 */
	public static String formatJavadoc(String comments, int indent, boolean closeJavadoc)
	{
		if(comments == null) comments = "";

		String[] sarr = comments.split("\\n");

		String tabs = indent > 0 ? chain("\t", indent) : "";

		StringBuilder sb = new StringBuilder();

		sb.append(tabs).append(closeJavadoc ? "/**" : "").append("\n");
		for(int i = 0; sarr != null && i < sarr.length; i++)
		{
			String line = sarr[i].trim();
			if(! line.isEmpty())
				sb.append(tabs).append(" * ").append(line).append("\n");
		}
		sb.append(tabs).append(closeJavadoc ? " */" : "").append("\n");

		return sb.toString();
	}

	/**
	 * Utility method to trim each line of the input text. If the input text
	 * contains lines separated by a newline character, each line will be
	 * trimmed. If the input text contains a single line (no newlines), the text
	 * will be trimmed.
	 *
	 * @param text
	 *            Input text containing one or more lines separated by a newline
	 *            character
	 * @return String representing the input text with each line trimmed
	 */
	public static String trimEachLine(String text)
	{
		if(text == null) text = "";

		String[] lines = text.split("\\n");

		StringBuilder sb = new StringBuilder();

		for(int i = 0; lines != null && i < lines.length; i++)
			sb.append(lines[i].trim()).append("\n");

		return sb.toString();
	}

	/**
	 * Convert a comma delimited String into an array of String values. For
	 * example, if delimitedString is "aaa,bbb,ccc", the return value would be
	 * {"aaa", "bbb", "ccc"}. If the delimitedString is "aaabbbccc", the return
	 * value would be {"aaabbbccc"}. If the delimitedString is an empty string,
	 * the return value would {""}. If the delimitedString is null, the return
	 * value would be a null.
	 *
	 * @param delimitedString
	 *            Comma delimited String
	 * @return String array representing the comma separated values from the
	 *         input string in that order.
	 * @see #delimitedStringToStringArray(String)
	 */
	public static String[] delimitedStringToStringArray(
			String delimitedString)
	{
		return delimitedStringToStringArray(
				delimitedString, ",");
	}

	/**
	 * Convert a comma delimited String into an array of String values. This
	 * method behaves identical to delimitedStringToStringArray(String) method,
	 * but provides an additional parameter that can be used to set the
	 * delimiter itself (if a value other than "," is desired as the delimiter).
	 *
	 * @param delimitedString
	 *            String delimited by the given delimiter
	 * @return String array representing the delimited values from the input
	 *         string in that order.
	 *
	 * @see #delimitedStringToStringArray(String)
	 */
	public static String[] delimitedStringToStringArray(
			String delimitedString, String delimiter)
	{
		String[] strArray = null;

		if(delimiter == null) delimiter = ",";

		if(delimitedString != null)
		{
			if(delimitedString.indexOf(delimiter) < 0)
				strArray = new String[] { delimitedString };
			else
				strArray = delimitedString.split(delimiter);
		}

		return strArray;
	}

	public static void main(String[] args)
	{
		System.out.println("[" + derivePropertyName("getMagicSpell") + "]");
		System.out.println("[" + derivePropertyName("setXML") + "]");
		System.out.println("[" + deriveSetterName(derivePropertyName("getMagicSpell")) + "]");
		System.out.println("[" + deriveGetterName(derivePropertyName("setXML")) + "]");

		System.out.println(capitalizeFirstChar("applyDiscount99"));
	}
}
